/*
 * newtest.c
 *
 * Copyright (c) 2014 Jeremy Garff <jer @ jers.net>
 * Copyright (c) 2017 Daniel 'grindhold' Brendle
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 *     1.  Redistributions of source code must retain the above copyright notice, this list of
 *         conditions and the following disclaimer.
 *     2.  Redistributions in binary form must reproduce the above copyright notice, this list
 *         of conditions and the following disclaimer in the documentation and/or other materials
 *         provided with the distribution.
 *     3.  Neither the name of the owner nor the names of its contributors may be used to endorse
 *         or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */


#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <signal.h>
#include <stdarg.h>
#include <getopt.h>


#include "clk.h"
#include "gpio.h"
#include "dma.h"
#include "pwm.h"
#include "version.h"

#include "ws2811.h"

// defaults for cmdline options
#define TARGET_FREQ                              WS2811_TARGET_FREQ
#define GPIO_PIN                                 18
#define DMA                                      5
#define STRIP_TYPE				                 WS2811_STRIP_RGB		// WS2812/SK6812RGB integrated chip+leds
//#define STRIP_TYPE				             WS2811_STRIP_GBR		// WS2812/SK6812RGB integrated chip+leds
//#define STRIP_TYPE				             SK6812_STRIP_RGBW		// SK6812RGBW (NOT SK6812RGB)

#define WIDTH                                    149
#define HEIGHT                                   1
#define LED_COUNT                                (WIDTH * HEIGHT)

int width = WIDTH;
int height = HEIGHT;
int led_count = LED_COUNT;

ws2811_t ledstring =
{
    .freq = TARGET_FREQ,
    .dmanum = DMA,
    .channel =
    {
        [0] =
        {
            .gpionum = GPIO_PIN,
            .count = LED_COUNT,
            .invert = 0,
            .brightness = 255,
            .strip_type = STRIP_TYPE,
        },
        [1] =
        {
            .gpionum = 0,
            .count = 0,
            .invert = 0,
            .brightness = 0,
        },
    },
};

static uint8_t running = 1;

typedef struct parable {
    float squeeze;
    float x;
    float y;
    float speed;
    int color;
} Parable;

#define N_PARABLES 16 
Parable parables[N_PARABLES];

ws2811_led_t wavecolors[] =
{
    0x00201000,
    0x00101000,
    0x00202000,
    0x00200500,
    0x00151000,
    0x000f1003,
    0x00010010,
    0x00220010,
};

void parable_init(Parable *p) {
    p->squeeze = (float)((rand() % 20)+1)/300.0f;
    p->x = rand() % 2 > 0 ? 0.0f : 149.0f;
    p->y = 1.0f;
    p->speed = ((float)(rand() % 60)+1.0f) / 20000.0f;
    if (p->x > 0) p->speed *= -1.0f;
    p->color = wavecolors[rand() % 8];
}

void parable_check_done(Parable *p) {
    if (p->speed < 0) {
	if (p->x < 10) parable_init(p);
    } else {
	if (p->x > 159) parable_init(p);
    }
}

unsigned int mix_colors(unsigned int a, unsigned int b) {
    if (a == 0 && b==0) {
	return 0;
    }
    unsigned int target = 0x00000000;
    unsigned int i = 0;
    unsigned int component_a, component_b, mix = 0;
    for (i = 0; i < 4; i++) {
        component_a = a & 0xff;
        component_b = b & 0xff;
        a >>= 8;
        b >>= 8;
	mix = component_a+component_b;
        target |= (mix > 0x60 ? 0x60 : mix) << 24;
	if (i != 3)
            target >>= 8;
    }
    return target;
}

unsigned int parable_get_color(Parable* p, int x) {
    float factor = -p->squeeze * ((float)x - p->x)*((float)x - p->x) + p->y;
    if (factor <= 0)
	return 0;
    unsigned int target = 0x00000000;
    unsigned int color = p->color;
    int icomponent, i;
    float component;
    for (i = 0; i < 4; i++) {
       component = (float)(color & 0x000000ff);
       component *= factor;
       icomponent = (int)component;
       icomponent = icomponent < 0x00 ? 0x00 : icomponent;
       icomponent = icomponent > 0x60 ? 0x60 : icomponent;
       target |= (unsigned int) icomponent << 24;
       color >>= 8;
       if (i != 3)
           target >>= 8;
    }
    return target;
}

void wave_render(void)
{
    int x, i;
    unsigned int color;

    for (x = 0; x < width; x++)
    {
	color = 0x0;
	for (i = 0; i < N_PARABLES; i++) {
            Parable* p = &(parables[i]);
	    color = mix_colors(color, parable_get_color(p,x));
            parable_check_done(p);
	    p->x += p->speed;
	}
        ledstring.channel[0].leds[x] = (ws2811_led_t) color;
    }
}

static void ctrl_c_handler(int signum)
{
    running = 0;
}

static void setup_handlers(void)
{
    struct sigaction sa =
    {
        .sa_handler = ctrl_c_handler,
    };

    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
}

int main(int argc, char *argv[])
{
    ws2811_return_t ret;

    srand((unsigned int)time(NULL));

    int i = 0;
    for (i = 0; i < N_PARABLES; i++) {
        parable_init(&parables[i]);
    }

    setup_handlers();

    if ((ret = ws2811_init(&ledstring)) != WS2811_SUCCESS)
    {
        fprintf(stderr, "ws2811_init failed: %s\n", ws2811_get_return_t_str(ret));
        return ret;
    }

    while (running)
    {
        wave_render();

        if ((ret = ws2811_render(&ledstring)) != WS2811_SUCCESS)
        {
            fprintf(stderr, "ws2811_render failed: %s\n", ws2811_get_return_t_str(ret));
            break;
        }

        usleep(1000000 / 120);
    }

    ws2811_fini(&ledstring);

    printf ("\n");
    return ret;
}

